'''
@File    :   calculators.py
@Time    :   2025/06/27 16:18:48
@Author  :   ZhangQifeng 
'''
import copy
import json
import os
import random
from core.logger import get_logger
from datetime import datetime
# from no_classfy.file_op import _read_excel, write_into_file


log = get_logger(__name__)

def calRange(st1, st2, b=100):
    """
    网格幅度盈亏辅助计算

    """
    s = b * (1 - st1 / 100)
    if s * (1 + st2 / 100) - b > 0:
        log.info(f"b:{st1},s:{st2},ok:True")
    else:
        log.info(f"b:{st1},s:{st2},ok:False")


def calValue(bp, bamount, sp, samount, tp="6",is_gy=None):
    log.debug(f"bp, bamount, sp, samount:{bp, bamount, sp, samount}")
    bTotal = bp * bamount
    log.debug(f"bTotal:{bTotal}")

    if is_gy is None:
        rate = 0.85
    else:
        rate = 0.9
    log.debug(f"yj:bTotal:{bTotal*rate/10000}")
    b = bTotal + bTotal * rate / 10000

    if tp == "6":
        log.debug(f"sz:bTotal:{bTotal/100*0.001}")
        b += bTotal / 100 * 0.001

    b *= -1
    log.debug(f"b:{b}")

    spTotal = sp * samount
    log.debug(f"spTotal:{spTotal}")

    log.debug(f"yj:spTotal:{spTotal*rate/10000}")
    s = spTotal - spTotal * rate / 10000

    log.debug(f"ss:spTotal:{spTotal*0.5/1000}")
    s = s - spTotal * 0.5 / 1000

    if tp == "6":
        s = s - spTotal / 100 * 0.001
        log.debug(f"sz:spTotal:{spTotal/100*0.001}")

    total = s + b
    log.info(f">>>>> total:{total:.2f}")
    return total


def calETFValue(bp, bamount, sp, samount, sellAmount=True):
    # sourcery skip: raise-specific-error
    """
    ETF计算盈余,基于银河万0.5，最低0.1费率。
    """
    log.debug(f"bp, bamount, sp, samount:{bp, bamount, sp, samount}")
    if bamount < samount:
        raise Exception("卖出大于买入数量")
    if sellAmount and bamount > samount:
        bamount = samount
    bTotal = bp * bamount
    bf = bTotal * 1 / 2 / 10000
    bf = max(bf, 0.1)
    b = bTotal + bf
    b *= -1

    spTotal = sp * samount
    sf = spTotal * 1 / 2 / 10000
    sf = max(sf, 0.1)
    s = spTotal - sf

    total = s + b
    log.info(f">>>>> total:{total:.2f}")
    return total


def calCountRange(initValue, step, count):
    r = initValue
    for _ in range(count):
        r *= 1 - step / 100
    log.info(
        f"{initValue}--{count} times ,range:{(r-initValue)/initValue*100:.2f}%,value:{r:.2f}"
    )


def ssc(count, needNum):
    total = 0
    for _ in range(count):
        res = f"{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}{random.randint(0, 9)}"
        # log.debug(f">>> {res}")
        if res in needNum:
            total += 1
    log.info(f"strike target count:{total},rate:{total/count*100:.2f}%")


def totalETF(fNm, sheet="Sheet1"):
    # sourcery skip: avoid-builtin-shadow, move-assign-in-block, sum-comprehension
    """
    根据给定的文件，读取数据，并根据计算函数，计算盈余，作出汇总
    {'A':{'b':[(1,25,1800),],'s':[(1,26,1800),]}}
    """
    xlsFile = os.path.join("inDirs", fNm)
    st = _read_excel(xlsFile, sheet)
    rows = st.nrows
    rowValue = {}
    sum = 0
    for row in range(rows):
        if row < 1:
            continue
        # log.debug(f"check DATA:{st.row_values(row)}")
        if st.cell_value(row, 6) == 1.0:
            if st.cell_value(row, 1) not in rowValue:
                rowValue[st.cell_value(row, 1)] = {
                    st.cell_value(row, 5): [
                        (st.cell_value(row, 3), st.cell_value(row, 4))
                    ]
                }
            else:
                # log.debug(f"this rowValue:{rowValue}\n{rowValue.get(st.cell_value(row,1))}")
                if st.cell_value(row, 5) not in rowValue.get(
                    st.cell_value(row, 1)
                ):
                    rowValue.get(st.cell_value(row, 1)).update(
                        {
                            st.cell_value(row, 5): [
                                (st.cell_value(row, 3), st.cell_value(row, 4))
                            ]
                        }
                    )
                else:
                    rowValue.get(st.cell_value(row, 1)).get(
                        st.cell_value(row, 5)
                    ).append((st.cell_value(row, 3), st.cell_value(row, 4)))
    log.debug(f"totalETF:{json.dumps(rowValue,indent=4,ensure_ascii=False)}")
    result = []
    try:
        for key in rowValue:
            b_values = rowValue[key].get("b", [])
            s_values = rowValue[key].get("s", [])
            result.extend(b + s for b, s in zip(b_values, s_values))
    except Exception as err:
        log.error(f"error:\nkeyerr,not found:{err}\nitem-{key}")
    for item in result:
        log.debug(f"item:{item}")
        sum += calETFValue(*item)
    log.info(f"total result:{sum}")


class TradeMan:
    def __init__(
        self, fileNm, inDate=None, byTime=True, remainDo=True
    ) -> None:
        self.__file = os.path.join("inDirs", fileNm)
        self.__rcd = None
        self.__operationData = None
        self.__classifyData = None
        self.__nowData = (
            datetime.now().strftime("%Y%m%d") if inDate is None else inDate
        )
        self.__byTime = byTime
        self.__remainDo = remainDo

    def __readRecord(self):
        """
        读数据
        """
        try:
            with open(self.__file, "r", encoding="gbk") as f:
                rcd = f.read()
                log.debug(f"readRecord >>>\n{rcd}")
                self.__rcd = rcd
        except Exception as err:
            log.error(err)
            raise err

    def __dataTolis(self):
        """
        数据加工
        """
        rlis = self.__rcd.split("\n")[1:]
        rlis = rlis[:-1]
        tmpLis = [item.split("\t") for item in rlis]
        log.debug(tmpLis)
        res = [
            [
                self.__nowData,
                item[1],
                item[2],
                item[4],
                item[5],
                item[6],
                item[7],
            ]
            for item in tmpLis
        ]
        self.__operationData = res
        log.debug(self.__operationData)

    def __classify(self):
        """
        数据分类
        """
        rs = {}
        for item in self.__operationData:
            tradeType = "b" if item[3] in ("证券买入", "融券") else "s"
            if item[2] not in rs:
                rs[item[2]] = {
                    tradeType: [[item[1], item[4], item[5], item[6], 0]]
                }
            elif tradeType in rs[item[2]]:
                rs[item[2]][tradeType].append(
                    [item[1], item[4], item[5], item[6], 0]
                )
            else:
                rs[item[2]][tradeType] = [
                    [item[1], item[4], item[5], item[6], 0]
                ]
        self.__classifyData = rs
        log.debug(self.__classifyData)

    # 定义排序键函数
    def __sort_time(self, item):
        return datetime.strptime(item[0], "%H:%M:%S")

    def __match(self):
        """
        匹配数据
        """
        import copy

        for pro, rcd in self.__classifyData.items():
            log.debug(f"this rcd:{rcd}")
            if pro == "204001":
                continue
            # 0卖出记录
            if len(rcd.get('s', [])) == 0:
                if len((rcd["b"])) == 0:                    
                    continue
                elif len(rcd.get("b", [])) > 0:
                    for item in rcd['b']:
                        # 变更数据处理状态
                        item[-1] = 1
                        # 追加ord价格
                        item.append(item[1])
            else:
                sTmp = copy.deepcopy(rcd.get("s", []))
                bTmp = copy.deepcopy(rcd.get("b", []))
                log.debug(f"====init====\nbTmp:{bTmp} \n sTmp:{sTmp}")

                def __scanBuy(value, time, datas, byTime=True):
                    """
                    从未处理的买记录扫描、比对，找出满足的未处理数据、早于卖出数据、小于卖出数据
                    241119 + 数量大于100
                    """
                    lowLimit = 100
                    for idx, i in enumerate(datas):
                        # log.debug(f"i >>> {i}")
                        
                        if byTime:
                            if (
                                i[4] == 0
                                and int(i[2]) >= lowLimit
                                and datetime.strptime(i[0], "%H:%M:%S")
                                < datetime.strptime(time, "%H:%M:%S")
                                and float(i[1]) < value
                            ):
                                return idx
                        elif (
                            i[4] == 0
                            and int(i[2]) >= lowLimit
                            and float(i[1]) < value
                        ):
                            return idx

                def __scanData(sTmp, bTmp):
                    """
                    遍历数据，卖和买数据匹配
                    """
                    for idx, sItem in enumerate(sTmp):
                        # log.debug(f"__scanData::sItem:{sItem}")
                        if sItem[4] == 1:
                            if len(sTmp) == idx + 1:
                                return 0, sTmp, bTmp
                            else:
                                continue
                        sValue = float(sItem[1])
                        sTime = sItem[0]
                        sAmount = int(sItem[2])
                        index = __scanBuy(
                            sValue, sTime, bTmp, byTime=self.__byTime
                        )
                        if index is not None:
                            # log.debug(f"__scanData: bTmp[index]:{bTmp[index]}")
                            # 3、卖出数量等于买入数量,修改买卖数据处理状态，同时买卖记录追加订单价格
                            if sAmount == int(bTmp[index][2]):
                                sItem[4] = 1
                                sItem.append(bTmp[index][1])
                                bTmp[index][4] = 1
                                bTmp[index].append(bTmp[index][1])
                            # 2、卖出数量小于买入数量,买入订单拆分多条，卖记录处理完毕
                            elif sAmount < int(bTmp[index][2]):
                                sItem[4] = 1
                                sItem.append(bTmp[index][1])
                                b = copy.deepcopy(bTmp[index])
                                bTmp[index][2] = sAmount
                                bTmp[index][4] = 1
                                bTmp[index].append(bTmp[index][1])
                                b[4] = 0
                                bTmp.append(b)
                                bTmp[-1][2] = int(bTmp[-1][2]) - sAmount
                                bTmp = sorted(
                                    bTmp,
                                    key=lambda x: datetime.strptime(
                                        x[0], "%H:%M:%S"
                                    ),
                                )
                                return 1, sTmp, bTmp
                            # 1、卖出数量大于买入数量
                            elif sAmount > int(bTmp[index][2]):
                                sNew = copy.deepcopy(sItem)
                                sItem[4] = 1  # 状态
                                sItem[2] = bTmp[index][2]  # 数量
                                sItem.append(bTmp[index][1])  # 订单
                                sNew[4] = 0
                                sNew[2] = int(sNew[2]) - int(bTmp[index][2])
                                sTmp.append(sNew)
                                # 按时间排序，优先按时间数据处理
                                sTmp = sorted(sTmp, key=lambda x: x[0])
                                bTmp[index][4] = 1
                                bTmp[index].append(bTmp[index][1])
                                return 1, sTmp, bTmp
                        if len(sTmp) == idx + 1:
                            return 0, sTmp, bTmp

                def __buyRcdToOrd(datas):
                    """
                    未能匹配卖数据的买数据修改为已处理、添加订单
                    """
                    for item in datas:
                        if item[4] == 0:
                            item[4] = 1
                            item.append(item[1])
                    return datas

                runTag = 1
                while runTag == 1:
                    runTag, sTmp, bTmp = __scanData(sTmp, bTmp)

                if self.__remainDo:
                    bTmp = __buyRcdToOrd(bTmp)

                # 使用 sorted 函数进行排序
                bTmp = sorted(bTmp, key=self.__sort_time)
                sTmp = sorted(sTmp, key=self.__sort_time)
                rcd["s"] = sTmp
                rcd["b"] = bTmp

            log.debug(f"after match >>> \n{self.__classifyData}")

    def __combineData(self):
        """
        重新整合匹配后的数据，讲结果写入新的txt文档
        """
        result = []
        for prod, types in self.__classifyData.items():
            for doType, entries in types.items():
                for entry in entries:
                    # 确保 ord 字段存在
                    if len(entry) == 4:
                        entry.append("")
                    if prod == "204001":
                        if doType == "b":
                            result.append(
                                [
                                    self.__nowData,
                                    entry[0],
                                    prod,
                                    "",
                                    doType,
                                    entry[1],
                                    str(entry[2]),
                                    str(int(entry[2]) * 100),
                                    "",
                                    "",
                                    "",
                                    "",
                                    "",
                                    "",
                                    str(100),
                                ]
                            )
                        else:
                            result.append(
                                [
                                    self.__nowData,
                                    entry[0],
                                    prod,
                                    "",
                                    doType,
                                    entry[1],
                                    str(entry[2]),
                                    str(entry[3]),
                                    "",
                                    "",
                                    "",
                                    "",
                                    "",
                                    "",
                                    str(100),
                                ]
                            )
                    else:
                        # log.debug(f">>> entry: \n{entry}")
                        result.append(
                            [
                                self.__nowData,
                                entry[0],
                                prod,
                                "",
                                doType,
                                entry[1],
                                str(entry[2]),
                                "",
                                "",
                                "",
                                "",
                                "",
                                "",
                                "",
                                str(entry[5]) if len(entry) >= 6 else "",
                            ]
                        )

        # 按时间排序
        result.sort(key=lambda x: datetime.strptime(x[1], "%H:%M:%S"))

        log.debug(f"result:\n{result}")
        
        def __listToText(lst):
            third_element = lst[2]
            if isinstance(third_element, int) and 0 <= third_element < 1000000:
                lst[2] = f"{third_element:06d}"
            elif isinstance(third_element, str) and third_element.isdigit() and 0 <= int(third_element) < 1000000:
                lst[2] = f"{int(third_element):06d}"
            return "\t".join(map(str, lst))

        content = "日期	成交时间	证券代码	证券名称	买卖标志	成交价格	成交数量	成交金额	股东代码	交易市场	成交编号	对方席位代码	约定序号	证券全称	备注\n"
        for line in result:
            content += __listToText(line)
            content += "\n"
        log.debug(content)

        file = (
            f"历史成交{self.__nowData}_match"
            if self.__remainDo is False
            else f"历史成交{self.__nowData}"
        )

        return write_into_file(
            "broken", "match", content, fileNm=file, encoding="gbk"
        )

    def do(self):
        """
        执行逻辑控制
        """
        self.__readRecord()
        self.__dataTolis()
        self.__classify()
        self.__match()
        self.__combineData()


def tradeTotal(file,t='etf'):
    with open(file, "r") as f:
        content = f.read()
    lines = content.split("\n")
    # log.debug(f"lines:\n{lines}")
    res = {}
    for idx, line in enumerate(lines):
        if idx < 1 or line == "":
            continue
        item = line.split("\t")
        # log.debug(f"this item:{item}\nres:\n{res}")
        if item[14] == "" or item[2] == "204001":
            continue
        if item[2] not in res:
            res[item[2]] = {
                item[14]: {item[4]: [(float(item[5]), int(item[6]))]}
            }
        elif item[14] not in res[item[2]]:
            res[item[2]][item[14]] = {
                item[4]: [(float(item[5]), int(item[6]))]
            }
        elif item[4] not in res[item[2]][item[14]]:
            res[item[2]][item[14]][item[4]] = [(float(item[5]), int(item[6]))]
        else:
            res[item[2]][item[14]][item[4]].append(
                (float(item[5]), int(item[6]))
            )
    log.debug(f"tradeTotal\n{res}\n ****************")
    result = []

    def __match_data(bData, sData):
        """
        买卖价格匹配
        """
        bCopy = copy.deepcopy(bData)
        sCopy = copy.deepcopy(sData)
        res = []

        def __op(a, b):
            rs = []
            for b in bCopy:
                for s in sCopy:
                    if b[0] < s[0] and b[1] == s[1]:
                        rs.append(b + s)
                        bCopy.remove(b)
                        sCopy.remove(s)
                        break
            return rs, bCopy, sCopy

        while len(bCopy) > 0:
            rs, bCopy, sCopy = __op(bCopy, sCopy)
            res.extend(rs)
        return res

    for prod, rcd in res.items():
        if t=='etf' and prod in ["159920", "159892"]:
            for ord, transactions in rcd.items():
                # log.debug(f"transactions:{transactions}")
                b_transactions = transactions.get('b', None)
                s_transactions = transactions.get('s', None)
                if b_transactions is None or s_transactions is None:
                    continue
                result.extend(__match_data(b_transactions, s_transactions))
                log.debug(f"result:\n{result}")
                total = sum(calETFValue(*item) for item in result)
        elif t == 'st' and prod in ["002479"]:
            for ord, transactions in rcd.items():
                # log.debug(f"transactions:{transactions}")
                b_transactions = transactions.get('b', None)
                s_transactions = transactions.get('s', None)
                if b_transactions is None or s_transactions is None:
                    continue
                result.extend(__match_data(b_transactions, s_transactions))
                log.debug(f"result:\n{result}")
                total = sum(calValue(*item) for item in result)
    log.debug(f"sum:{total:.2f}")


def solution(init, min, amount, 
             priceStep=None, 
             priceRange=None, 
             amountStep=None, 
             amountRange=None,
             payMin=None,
             type='stock',
             payDividend=None
             ):
    # sourcery skip: raise-specific-error
    """
    向下设置方案，推导出详情
    priceRange:15%
    payDividend:每10股派现
    """
    totalAmount = amount
    total = init*amount
    count = 1
    content = f'\n*****init:{init},amount:{amount},min:{min}'
    if priceStep is not None:
        if type=='stock':
            content+=f',priceStep:{priceStep:.2f}'
        else:
            content+=f',priceStep:{priceStep:.3f}'
    elif priceRange is not None:
        if type=='stock':
            content+=f',priceStep:{priceStep:.2f}'
        else:
            content+=f',priceStep:{priceStep:.3f}'
    if amountStep is not None:
        content+=f',amountStep:{amountStep}'
    elif amountRange is not None:
        content+=f',amountRange:{amountRange}'
    content += '*****'
    if type=='stock':
        content += f'\n{count:0>3}\t{init:.2f}\t{amount}\t{total:.3f}\t{totalAmount}\t{total:.3f}'
    else:
        content += f'\n{count:0>3}\t{init:.3f}\t{amount}\t{total:.3f}\t{totalAmount}\t{total:.3f}'
        
    if payDividend is not None:
        content+= f'\t{payDividend/10/init*100:.2f}%'
        
    while init > min:
        if priceStep is not None:
            if priceStep > 0:
                init -= priceStep
            else:
                err = '差价设置存在时，必须大于0'
                log.error(err)
                raise Exception(err)
        elif priceRange is not None:
            if priceRange > 0:
                init = (1-priceRange/100)*init
            else:
                err = '差价幅度设置存在时，必须大于0'
                log.error(err)
                raise Exception(err)
        if amountStep is not None:
            if  amountStep%100==0:
                amount += amountStep
            else:
                err = '数量设置存在时，必须是100的倍数'
                log.error(err)
                raise Exception(err)
        elif amountRange is not None:
            if amountRange > 0:
                amount = int((1+amountRange/100)*amount/100)*100
            else:
                err = '数量幅度设置存在时，必须大于0'
                log.error(err)
                raise Exception(err)
        count += 1        
        pay = init*amount
        if payMin is not None and pay<payMin:
            amount+=100
            pay=init*amount
        totalAmount +=amount
        total += pay
        if type=='stock':
            content += f'\n{count:0>3}\t{init:.2f}\t{amount}\t{pay:.3f}\t{totalAmount}\t{total:.3f}'
        else:
            content += f'\n{count:0>3}\t{init:.3f}\t{amount}\t{pay:.3f}\t{totalAmount}\t{total:.3f}'
        
        if payDividend is not None:
            content+= f'\t{payDividend/10/init*100:.2f}%'
    log.info(content)


def calculate_installments_with_extended_available(total_amount, installments, interest_amount=None):
    """
    
    """
    if installments <= 0:
        raise ValueError("分期数必须大于0")

    per_payment = total_amount / installments
    available_loan_sum = 0
    remaining = total_amount

    # 第 0 期（未还款）
    available_loan_sum += remaining

    # 接下来的 installments 期
    for _ in range(installments):
        remaining -= per_payment
        # log.debug(f"第 {_ + 1} 期：还了 {per_payment}，还剩 {remaining},累计已用 {available_loan_sum}")
        available_loan_sum += remaining

    result = {
        "每期还款金额": round(per_payment, 2),
        "累计可用贷款金额": round(available_loan_sum, 2),
        "平均月可用":  round(available_loan_sum / installments, 2),
    }

    if interest_amount is not None:
        if interest_amount < 0:
            raise ValueError("利息总额不能为负数")
        interest_ratio = interest_amount / available_loan_sum
        result["利息占比"] =  f'{round(interest_ratio, 4)*100:.2f}%'
    
    result['年化'] = f'{round(interest_ratio, 4)*100*12:.2f}%'

    return result


if __name__ == "__main__":
    
    
    # solution(
    #     0.98, 0.713, 2200,
    #     priceStep=0.005*0.98,
    #     payMin=2000,
    #     # amountRange=10,
    #     # amountStep=100,
    #     type='etf',
    #     # payDividend=1.275*0.05*10
    # )
    # solution(
    #     0.940, 0.869, 1800,
    #     priceStep=0.005*0.940,
    #     payMin=2000,
    #     # amountRange=10,
    #     # amountStep=100,
    #     type='etf',
    #     # payDividend=1.275*0.05*10
    # )
    #沪深300,目标：3.959  3.7预计有7个点
    # solution(
    #     3.7, 2.997, 700,
    #     priceStep=0.015,
    #     payMin=2000,
    #     # amountRange=10,
    #     # amountStep=100,
    #     type='etf',
    #     # payDividend=1.275*0.05*10
    # )
    # 创业板ETF159952
    # solution(
    #     1.15, 0.684, 1800,
    #     priceStep=0.005,
    #     payMin=2000,
    #     # amountRange=10,
    #     # amountStep=100,
    #     type='etf',
    #     # payDividend=1.24*0.05*10
    # )    
    # calETFValue(1.24, 2500, 1.245, 2500)
    # calValue(16.2, 200, 16.22, 200)
   #
    # TradeMan(
    #     "历史成交0306.txt",
    #     inDate='20250306',
    #     byTime=True,  # 全天交易匹配模式, False:先买后入匹配
    #     remainDo=True,  # True则所有买单都有单价
    # ).do()

    # tradeTotal(r'broken\match\历史成交20250306_match.txt',t='st')
    
    # # 恒生医疗ETF 513060
    # solution(
    #     0.381, 0.331, 6000,
    #     priceStep=0.001,
    #     payMin=2000,
    #     # amountRange=10,
    #     # amountStep=100,
    #     type='etf',
    # )
    # calETFValue(1.35, 3800, 1.351, 3800)
    # 富春环保
    # solution(
    #     5.05, 3.28, 600,
    #     priceStep=0.03,
    #     # amountStep=100,
    #     payMin=2352.94,
    #     payDividend=1.5
    # )
    calValue(5.76, 500, 5.8, 500,is_gy=True)  
    # rsp = calculate_installments_with_extended_available(50000,12,840)
    # log.debug(rsp)
    